<!-- **
    * @description upload 图片/视频/音频/文件组件 [image/video/txt/docx/xsl/pdf等]
** -->
<!-- **
    * @use 无需引入直接使用 <yfn-upload @success="successUpload"></yfn-upload>
    * const successUpload = (e: any) => { // e.url}
    * @params参数说明：
    * @params {accept}                 上传文件类型，默认 image/*，[video/*、pdf、png、jpg、docx、.xlsx等]
    * @params {min-size}               最小上传文件，eg: 0.5 -> 最小上传 0.5MB
    * @params {max-size}               最大上传文件，eg: 3 -> 最大上传 3MB
    * @params {file-name}              上传文件名称，默认 当前时间戳
    * @params {disabled}               是否禁用，默认 false
    * @params {multiple}               是否支持多文件上传，默认 false
    * @params {limit}                  限制上传文件的数量。0表示不限制，默认 0
    * @params {on-before-upload}       上传文件前触发
    * @params {...}                    继承 a-upload 原生属性/方法/事件
** -->
<!-- **
    * @events事件：
    * @success                         上传成功
    * @before                          上传前触发
    * @get-instance                    获取上传组件实例
    * @{...}                           继承 a-upload 原生事件
** -->
<!-- **
    * @slots插槽：
    * @upload-button                   上传按钮
    * @{...}                           继承 a-upload 原生事件
** -->
<template>
  <a-upload class="YfnUpload" ref="YfnUpload" v-bind="{ ...defaultBind, ...attrs }" :custom-request="uploadSuccess">
    <!-- 插槽 -->
    <template v-for="item in biz.slotList" :key="item" #[item]>
      <slot :name="item"></slot>
    </template>
    <template v-if="!slots['upload-button']" #upload-button>
      <a-button type="primary">{{ props.buttonText || '上传文件' }}</a-button>
    </template>
  </a-upload>
</template>

<script lang="ts" setup>
  import { reactive, defineProps, useAttrs, useSlots, getCurrentInstance } from 'vue'
  import { Message } from '@arco-design/web-vue'
  import OSS from 'ali-oss'
  import dayjs from 'dayjs'
  import { getStsToken } from '@/api/task'
  /* props */
  const props = defineProps({
    buttonText: String,
    minSize: [String, Number],
    maxSize: [String, Number],
    fileName: [String, Number],
    hasBuffer: {
      type: Boolean,
      default: () => {
        return true
      },
    },
  })
  /* attrs */
  const attrs = useAttrs()
  /* proxy */
  const { proxy } = getCurrentInstance() as any
  /* emit */
  const emit = defineEmits(['success', 'before', 'get-instance'])
  /* slots */
  const slots = useSlots()
  /* data */
  const defaultBind: any = reactive({
    action: '',
    accept: 'image/*',
    'show-file-list': false,
  })
  const defaultOptions: any = reactive({
    hasBuffer: true,
  })
  const options: any = reactive({
    ...defaultOptions,
    ...props,
  })
  const biz: any = reactive({
    ossKey: {},
    slotList: [],
  })
  /* fn */
  const setBind = () => {
    attrs['on-before-upload'] ?? (defaultBind['on-before-upload'] = beforeUpload)
  }
  const getSlot = () => {
    biz.slotList = Object.keys(slots).map((key: string) => {
      return key
    })
  }
  getSlot()
  const getInstance = () => {
    emit('get-instance', proxy)
  }
  getInstance()
  // valid-上传
  const beforeUpload: any = (e: any) => {
    const valid = validSize(e, { minSize: options.minSize, maxSize: options.maxSize })
    valid.msg && Message.error(valid.msg)
    emit('before', { ...valid })
    return valid.validStatus
  }
  setBind()
  const validSize: any = (e: any = {}, option: any = {}) => {
    // 文件大小校验
    const size = e.size / 1024 / 1024
    const minSize = option.minSize || 0
    const maxSize = option.maxSize || 99999
    const symbol = {
      min: minSize >= 1 ? 'M' : 'KB',
      max: maxSize >= 1 ? 'M' : 'KB',
    }
    const value = {
      min: minSize >= 1 ? minSize : minSize * 1024,
      max: maxSize >= 1 ? maxSize : maxSize * 1024,
    }
    const check = {
      min: minSize && minSize > size,
      max: maxSize && maxSize < size,
    }
    let msg = ''
    check.min && (msg = `最小上传 ${value.min}${symbol.min}`)
    check.max && (msg = `最大上传 ${value.max}${symbol.max}`)
    const r = { msg, symbol, value, check, validStatus: !msg, size: size * 1024 }
    return r
  }
  // submit-上传
  const uploadSuccess = (e: any) => {
    // 文件上传处理
    const originFile = e.fileItem?.file || {}
    const fileNameStamp = new Date().getTime()
    const fileTypeIndex = originFile.name.lastIndexOf('.')
    const originFileName = originFile.name.slice(0, fileTypeIndex)
    const replaceFileName = originFileName.replaceAll(' ', '').replaceAll('/', '_')
    const fileType = originFile.name.slice(fileTypeIndex + 1)
    const folderName = `${dayjs().year()}-${(dayjs().month() + 1).toString().padStart(2, '0')}`
    const fullFillName = options.fileName || `${replaceFileName}_${fileNameStamp}`
    const fileName = `${fullFillName}.${fileType}`
    const abbFileName = fileName
    const validOptions = { minSize: options.minSize, maxSize: options.maxSize }
    const validValue = validSize(originFile, validOptions)
    let bizOptions: any = { fileTypeIndex, originFileName, replaceFileName, fileType, folderName, fullFillName, fileName, abbFileName, fileNameStamp, validValue }
    // Blob 转 Buffer
    blobToBuffer(originFile).then((res: any) => {
      bizOptions.buffer = res
      bizOptions.aliOption = {
        file: bizOptions.buffer.url,
        folderName: `task/${bizOptions.folderName}/`,
        domain: 'https://cdn2.selleroa.com/',
        fileType: bizOptions.fileType,
        fileName: bizOptions.fileName,
        secure: true,
      }
      // 文件上传到 ali-oss
      aliOssUpload(bizOptions.aliOption).then((res: any) => {
        bizOptions.oss = res
        bizOptions.url = res.reUrl
        bizOptions.origin = originFile
        bizOptions.options = options
        emit('success', { ...bizOptions, ...biz })
      })
    })
  }
  const blobToBuffer = (blob: any) => {
    // blob 转 buffer
    return new Promise((resolve) => {
      const read = new FileReader()
      read.onload = (e: any) => {
        const o = {
          ...e,
          url: e.target.result,
        }
        resolve(o)
      }
      if (options.hasBuffer) {
        read.readAsArrayBuffer(blob)
      } else {
        read.readAsDataURL(blob)
      }
    })
  }
  const aliOssUpload = (aliOption: any) => {
    // ali-oss 上传
    return new Promise((resolve) => {
      let Buffer = OSS.Buffer
      const uploadPath = `${aliOption.folderName}${aliOption.fileName}`
      const localFile = Buffer.from(aliOption.file, 'base64')

      if (!OSS) return
      if (!aliOption.file) return

      getOssKey().then((res: any) => {
        const client = new OSS({
          accessKeyId: res.accessKeyId,
          accessKeySecret: res.accessKeySecret,
          stsToken: res.securityToken,
          bucket: res.bucket || 'static-erp',
          region: res.endpoint || 'oss-cn-shenzhen',
          secure: true,
        })
        client
          .put(uploadPath, localFile)
          .then((res: any) => {
            resolve({
              reUrl: `${aliOption.domain}${res.name}`,
              ...res,
            })
          })
          .catch(() => {
            biz.ossKey = {}
          })
      })
    })
  }
  const getOssKey = () => {
    // 获取 ali-oss 秘钥
    return new Promise((resolve, reject) => {
      if (biz.ossKey.securityToken) {
        resolve(biz.ossKey)
        return false
      }
      getStsToken()
        .then((response: any) => {
          const res = response.data
          biz.ossKey = res
          resolve(res)
        })
        .catch((e: any) => {
          Message.error('获取直传KEY失败')
          reject(e)
        })
    })
  }
</script>

<style scoped lang="less"></style>
